# -*- coding: utf-8 -*-
from math import *
import random as r

class XLeaf(object):
	def __init__(self, gene, parent):
		self.gene = gene
		gene.register(self)
		self.parent = parent

	def clone(self, gene, parent):
		return XLeaf(gene, parent)

	def __call__(self, x, y=None):
		return x
	
	def __str__(self):
		return "x"

	def restartRegistration(self, gene):
			self.gene = gene
			self.gene.register(self)


class YLeaf(XLeaf):
	def __init__(self, gene, parent):
		super(YLeaf, self).__init__(gene, parent)

	def clone(self, gene, parent):
		return YLeaf(gene, parent)

	def __call__(self, x, y=None):
		return y
	
	def __str__(self):
		return "y"

class ConstanteLeaf(XLeaf):
	def __init__(self, gene, parent, constante):
		super(ConstanteLeaf, self).__init__(gene, parent)
		self.constante = constante

	def clone(self, gene, parent):
		return ConstanteLeaf(gene, parent, self.constante)

	def __call__(self, x, y=None):
		return self.constante
	
	def __str__(self):
		return `self.constante`

	def change(self):
		if isinstance(self.parent, XLeaf):
			self.parent.change(self)

ALL_READERS = {}

def availableReaders():
	return ALL_READERS.keys()

class ReaderRegistrable(type):
	def __new__(metacls, name, bases, dct):
		cls = type.__new__(metacls, name, bases, dct)
		if name != "AbstractReader":
			if cls.__name__ not in availableReaders():
				ALL_READERS[cls.__name__] = cls
		return cls

class AbstractReader(object):
	__metaclass__ = ReaderRegistrable


ALL_FORMULAS_HASH = {}
ALL_FORMULAS = []

class FormulaNodeRegistrable(type):
	def __new__(metacls, name, bases, dct):
		cls = type.__new__(metacls, name, bases, dct)
		if not name.startswith("Abstract"):
			if not (hasattr(cls, 'name') and type(cls.name) == str and cls.name):
					raise AttributeError('Formula node must have a name attribute')
			if not (hasattr(cls, 'formatage') and type(cls.formatage) == str and cls.formatage):
					raise AttributeError('Formula node must have a format attribute')
			if not (hasattr(cls, 'nParams') and type(cls.nParams) == int and cls.nParams):
					raise AttributeError('Formula node must have a nParams attribute')
			if not hasattr(cls, 'func'):
					raise AttributeError('Formula node must have a func attribute')
			if cls not in ALL_FORMULAS:
				ALL_FORMULAS_HASH[cls.__name__] = cls
				ALL_FORMULAS.append(cls)
		return cls

class AbstractFormulaNode(XLeaf):
	__metaclass__ = FormulaNodeRegistrable

	def __init__(self, gene, parent, childs = []):
		super(AbstractFormulaNode, self).__init__(gene, parent)
		self.childs = childs
		for child in self.childs:
			child.parent = self

	def clone(self, gene, parent):
		clonedChilds = [child.clone(gene, parent) for child in self.childs]
		return self.__class__(gene, parent, clonedChilds)

	def __call__(self, x, y=None):
		args = [child(x,y) for child in self.childs]
		return apply(self.func, args)

	def __str__(self):
		return self.__class__.formatage % tuple([child.__str__() for child in self.childs])

	def generateChilds(self, baseChild):
		self.childs = [baseChild]
		baseChild.parent = self
		for x in xrange(self.nParams-1):
			self.childs.append(self._getRandomLeaf())
			self.childs[-1].parent = self
		r.shuffle(self.childs)
	

	def _getRandomLeaf(self):
		random = r.random() #TODO si le signal prend qu'une seule dim en entre je peux renommer YFormula = XFormula
		if random < 0.25:
			return XLeaf(self.gene, self)
		elif random < 0.5:
			return YLeaf(self.gene, self)
		elif random < 0.75:
			return self._getRandomCte_propToLen()
		else:
			return self._getRandomCte_propToMean()
		
	def _getRandomCte_propToLen(self):
		random = r.random() - 0.5
		constante = self._filterConstante(random*self.gene.lenSignal*4)
		return ConstanteLeaf(self.gene, self, constante)

	def _getRandomCte_propToMean(self):
		random = r.random() - 0.5
		constante = self._filterConstante(random*self.gene.meanSignal*4)
		return ConstanteLeaf(self.gene, self, constante)

	def _filterConstante(self, constante):
		return constante

			
	def replaceChild(self, oldChild, newChild):
		position = self.childs.index(oldChild)
		#newChild.parent = self
		self.childs[position] = newChild

	def restartRegistration(self, gene):
			super(AbstractFormulaNode, self).restartRegistration(gene)
			for child in self.childs:
				child.restartRegistration(gene)

	def change(self, cteNode):
		shift = (r.random() - 0.5)*sqrt(abs(cteNode.constante))
		cteNode.constante = self._filterConstante(cteNode.constante+shift)


#==================================================
#================== Formulas ======================
#==================================================

class CosFormulaNode(AbstractFormulaNode):
	name = 'cos'
	func = staticmethod(lambda x: cos(x))
	formatage = 'cos(%s)'
	nParams = 1

	def __init__(self, gene, parent, childs = []):
		super(CosFormulaNode, self).__init__(gene, parent, childs)

	def generateChilds(self, baseChild):
		self.childs = [baseChild]
		baseChild.parent = self

class SinFormulaNode(CosFormulaNode):
	name = 'sin'
	func = staticmethod(lambda x: sin(x))
	formatage = 'sin(%s)'
	nParams = 1

	def __init__(self, gene, parent, childs = []):
		super(SinFormulaNode, self).__init__(gene, parent, childs)

class TanFormulaNode(CosFormulaNode):
	name = 'tan'
	func = staticmethod(lambda x: tan(x))
	formatage = 'tan(%s)'
	nParams = 1

	def __init__(self, gene, parent, childs = []):
		super(TanFormulaNode, self).__init__(gene, parent, childs)

class ArcTanFormulaNode(CosFormulaNode):
	name = 'atan'
	func = staticmethod(lambda x: atan(x))
	formatage = 'atan(%s)'
	nParams = 1

	def __init__(self, gene, parent, childs = []):
		super(ArcTanFormulaNode, self).__init__(gene, parent, childs)

class AbsFormulaNode(CosFormulaNode):
	__metaclass__ = FormulaNodeRegistrable
	name = 'abs'
	func = staticmethod(lambda x: abs(x))
	formatage = '|%s|'
	nParams = 1

	def __init__(self, gene, parent, childs = []):
		super(AbsFormulaNode, self).__init__(gene, parent, childs)

#################################################
class PlusFormulaNode(AbstractFormulaNode):
	__metaclass__ = FormulaNodeRegistrable
	name = 'plus'
	func = staticmethod(lambda x,y:x+y)
	formatage = '(%s)+(%s)'
	nParams = 2

	def __init__(self, gene, parent, childs = []):
		super(PlusFormulaNode, self).__init__(gene, parent, childs)

class MinusFormulaNode(AbstractFormulaNode):
	__metaclass__ = FormulaNodeRegistrable
	name = 'minus'
	func = staticmethod(lambda x,y:x-y)
	formatage = '(%s)-(%s)'
	nParams = 2

	def __init__(self, gene, parent, childs = []):
		super(MinusFormulaNode, self).__init__(gene, parent, childs)

class TimesFormulaNode(AbstractFormulaNode):
	__metaclass__ = FormulaNodeRegistrable
	name = 'times'
	func = staticmethod(lambda x,y:x*y)
	formatage = '(%s)*(%s)'
	nParams = 2

	def __init__(self, gene, parent, childs = []):
		super(TimesFormulaNode, self).__init__(gene, parent, childs)

class DivFormulaNode(AbstractFormulaNode):
	__metaclass__ = FormulaNodeRegistrable
	name = 'div'
	func = staticmethod(lambda x,y:x/y)
	formatage = '(%s)/(%s)'
	nParams = 2

	def __init__(self, gene, parent, childs = []):
		super(DivFormulaNode, self).__init__(gene, parent, childs)

	def _filterConstante(self, constante):
		if constante == 0:
			return 0.01
		return constante

class ModFormulaNode(DivFormulaNode):
	__metaclass__ = FormulaNodeRegistrable
	name = 'mod'
	func = staticmethod(lambda x,y:x%y)
	formatage = '(%s) mod (%s)'
	nParams = 2

	def __init__(self, gene, parent, childs = []):
		super(ModFormulaNode, self).__init__(gene, parent, childs)

class IntPowFormulaNode(AbstractFormulaNode):
	name = 'intpow'
	func = staticmethod(lambda x,y:abs(x)**y)
	formatage = '(%s)^(%s)'
	nParams = 2

	def __init__(self, gene, parent, childs = []):
		super(IntPowFormulaNode, self).__init__(gene, parent, childs)

	def _getRandomCte_propToComplexity(self):
		random = r.random() - 0.5
		constante = int(round(random*10))
		return ConstanteLeaf(self.gene, self, constante)

	def generateChilds(self, baseChild):
		self.childs = [baseChild]
		baseChild.parent = self
		coef = self._getRandomCte_propToComplexity()
		coef.parent = self
		self.childs.append(coef)

class AbsPowFormulaNode(IntPowFormulaNode):
	name = 'abspow'
	func = staticmethod(lambda x,y:abs(x)**y)
	formatage = '|%s|^(%s)'
	nParams = 2

	def _getRandomCte_propToComplexity(self):
		random = r.random() - 0.5
		constante = random*10
		return ConstanteLeaf(self.gene, self, constante)

class LogFormulaNode(AbstractFormulaNode):
	__metaclass__ = FormulaNodeRegistrable
	name = 'log'
	func = staticmethod(lambda x,y:log(x,y))
	formatage = 'log(%s,%s)'
	nParams = 2

	def __init__(self, gene, parent, childs = []):
		super(LogFormulaNode, self).__init__(gene, parent, childs)

	def _filterConstante(self, constante):
		if constante < 0:
			constante = abs(constante)
		if constante in (0,1):
			return constante+0.1
		return constante

class InfFormulaNode(AbstractFormulaNode):
	__metaclass__ = FormulaNodeRegistrable
	name = 'inf'
	func = staticmethod(lambda x,y:x<y)
	formatage = '(%s)<(%s)'
	nParams = 2

	def __init__(self, gene, parent, childs = []):
		super(InfFormulaNode, self).__init__(gene, parent, childs)


		
if __name__ == '__main__':
	class Gene(object):
		def __init__(self):
			pass

		def register(self, formula):
			pass

		def replaceChild(self, old, new):
			pass

	gene = Gene()
	f1 = XLeaf(gene, gene)
	f2 = CosFormulaNode(gene, gene)
	f2.generateChilds(f1)
	f1.parent.replaceChild(f1, f2)
	print "f2 string :", f2
	print "f2(0) :", f2(0)
	print "f2(0.5) (wanted:0.877):", f2(0.5)
	f3 = XLeaf(gene, gene)
	f4 = TanFormulaNode(gene, gene)
	f4.generateChilds(f3)
	f3.parent.replaceChild(f3, f4)
	f5 = PlusFormulaNode(gene, gene, [f2, f4])
	print "f5 string :", f5
	print "f5(0) :", f5(0)
	print "f5(0.5) (wanted:1.4238):", f5(0.5)

	print "ALL_FORMULA:", ALL_FORMULAS
